package com.katesoft.scale4j.persistent.spring.postprocessor;

import com.katesoft.scale4j.common.spring.AbstractBeanPropertiesOverridePostProcessor;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.core.Ordered;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import static com.katesoft.scale4j.common.services.IBeanNameReferences.SESSION_FACTORY;
import static com.katesoft.scale4j.common.spring.IPostProcessingOrder.HIBERNATE_SESSION_POST_PROCESSOR;

/**
 * This class allow to change configuration of hibernate session factory bean without changing of original spring configuration.
 *
 * @author kate2007
 */
public class HibernateSessionExtensionPostProcessor extends AbstractBeanPropertiesOverridePostProcessor implements InitializingBean, Ordered
{
    private List<?> mappingResources;
    private List<?> annotatedClasses;
    private List<?> configLocations;
    private Properties hibernateProperties;

    public HibernateSessionExtensionPostProcessor()
    {
        setTargetBean(SESSION_FACTORY);
        setOrder(HIBERNATE_SESSION_POST_PROCESSOR);
    }

    /** Add the annotated classes and the mapping resources to the existing Session Factory configuration. */
    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    protected void doPostProcessing(ConfigurableListableBeanFactory configurableListableBeanFactory)
    {
        overrideBeanProperties();
        if (mappingResources != null) {
            // do we have existing resources?
            PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("mappingResources");
            if (propertyValue == null) {
                propertyValue = new PropertyValue("mappingResources", new ArrayList());
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            // value is expected to be a list.
            List existingMappingResources = (List) propertyValue.getValue();
            existingMappingResources.addAll(mappingResources);
        }
        if (annotatedClasses != null) {
            // do we have existing resources?
            PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("annotatedClasses");
            if (propertyValue == null) {
                propertyValue = new PropertyValue("annotatedClasses", new ArrayList());
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            // value is expected to be a list.
            List existingAnnotatedClasses = (List) propertyValue.getValue();
            existingAnnotatedClasses.addAll(annotatedClasses);
        }
        if (configLocations != null) {
            PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("configLocations");
            if (propertyValue == null) {
                propertyValue = new PropertyValue("configLocations", new ArrayList());
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            List existingConfigLocations = (List) propertyValue.getValue();
            existingConfigLocations.addAll(configLocations);
        }
        if (hibernateProperties != null) {
            PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("hibernateProperties");
            if (propertyValue == null) {
                propertyValue = new PropertyValue("hibernateProperties", hibernateProperties);
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            else {
                Properties existingHibernateProperties;
                if (propertyValue.getValue() instanceof Properties) {
                    existingHibernateProperties = (Properties) propertyValue.getValue();
                }
                else {
                    PropertiesEditor propertiesEditor = new PropertiesEditor();
                    propertiesEditor.setAsText(((TypedStringValue) propertyValue.getValue()).getValue());
                    existingHibernateProperties = (Properties) propertiesEditor.getValue();
                }
                existingHibernateProperties.putAll(hibernateProperties);
                mutablePropertyValues.removePropertyValue("hibernateProperties");
                propertyValue = new PropertyValue("hibernateProperties", existingHibernateProperties);
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
        }
    }

    /**
     * Set list of mapping resources (.hbm.xml files) to be added to the session factory.
     *
     * @param mappingResources list of mapping resources.
     */
    public void setMappingResources(List<?> mappingResources)
    {
        this.mappingResources = mappingResources;
    }

    /**
     * The list of annotated classes to be added to the session factory.
     *
     * @param annotatedClasses list of annotated classes that need to be added.
     */
    public void setAnnotatedClasses(List<?> annotatedClasses)
    {
        this.annotatedClasses = annotatedClasses;
    }

    /**
     * Set list of configuration locations (i.e. classpath:hibernate.cfg.xml) to be added to the session factory
     *
     * @param configLocations The list of configuration locations that need to be added.
     */
    public void setConfigLocations(List<?> configLocations)
    {
        this.configLocations = configLocations;
    }

    /**
     * Hibernate properties to added to the session factory.
     *
     * @param hibernateProperties list of additional properties.
     */
    public void setHibernateProperties(Properties hibernateProperties)
    {
        this.hibernateProperties = hibernateProperties;
    }

    @Override
    public void afterPropertiesSet()
    {
        logger.info("Using SessionFactory = %s, mappingResources = %s, annotatedClasses = %s, configLocations = %s, hibernateProperties = %s",
                    getTargetBean(),
                    mappingResources == null ? "[]" : mappingResources.toString(),
                    annotatedClasses == null ? "[]" : annotatedClasses.toString(),
                    configLocations == null ? "[]" : configLocations.toString(),
                    hibernateProperties == null ? "[]" : hibernateProperties.toString());
    }
}
